/*********************************************************************************
 *      Copyright:  (C) 2020 wangruijie<1046299465@qq.com>
 *                  All rights reserved.
 *
 *       Filename:  serial.c
 *    Description:  This file 
 *                 
 *        Version:  1.0.0(09/07/20)
 *         Author:  wangruijie <1046299465@qq.com>
 *      ChangeLog:  1, Release initial version on "09/07/20 02:46:02"
 *                 
 ********************************************************************************/
#include "serial.h"

serial_t *comport_init(const char *dev_name, long baudrate, int databit, const char parity, int stopbit)
{
    serial_t        *serial = NULL;

    if(NULL == (serial = (serial_t *)malloc(sizeof(serial_t))))
    {
        return NULL;
    }

    memset(serial, 0, sizeof(serial_t));
    serial->used = 0;

    strncpy(serial->dev_name, DEV_NAME, DEVNAME_LEN);   //串口设备名字
    serial->databit = databit;          //数据位
    serial->parity = parity;              //奇偶校验位
    serial->stopbit = stopbit;            //停止位
    serial->baudrate = baudrate;          //波特率
    serial->frag_size = 128;

    printf("TTY Parameter [baudrate: %ld, databit: %d, parity: %c, stopbit: %d]\n", serial->baudrate, serial->databit, serial->parity, serial->stopbit);

    return serial;
}

/* 清理串口结构体 */
void comport_term(serial_t *serial)
{
    if(NULL == serial)
    {
        return;
    }

    if((tcsetattr(serial->fd, TCSANOW,&(serial->original_opt)))!=0) //设置回原来的串口属性
    {
        printf("tcsetattr failed:%s\n", strerror(errno));
        return;
    }

    if(serial->fd != 0)
    {
        comport_close(serial);
    }

    memset(serial, 0x00, sizeof(serial_t));
    free(serial);
    serial = NULL;

    return;
}

int comport_open(serial_t *serial)
{
    int               rv = -1;
    struct termios    options;

    serial->fd = open(serial->dev_name,O_RDWR|O_NOCTTY|O_NONBLOCK) ; //打开串口设备
    if(serial->fd < 0)
    {
        printf("open serial failure:%s\n", strerror(errno));
        return -1;
    }

    rv = tcgetattr(serial->fd, &(serial->original_opt)); //获取原有的串口属性的配置
    if(rv != 0)
    {
         printf("tcgetattr() get original options failed:%s\n",strerror(errno)) ;
         return -2;
    }

    rv = tcgetattr(serial->fd, &options); //获取原有的串口属性的配置
    if(rv != 0)
    {
        printf("tcgetattr() failed:%s\n",strerror(errno)) ;
        return -4;
    }

    options.c_cflag &= ~CSIZE;    //屏蔽其他标志位 
    options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);    
    options.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
    options.c_oflag &= ~(OPOST);   //修改输出模式，原始数据输出

    set_baudrate(&options, serial->baudrate) ; //设置波特率
    set_data_bit(&options, serial->databit) ;//设置数据位
    set_parity(&options, serial->parity) ; //设置校验位
    set_stopbit(&options, serial->stopbit) ; //设置停止位

    //设置等待时间和最小接收字符
    options.c_cc[VMIN] = 0;
    options.c_cc[VTIME] = 0;

    tcflush(serial->fd, TCIFLUSH); //清空缓存区

    if((tcsetattr(serial->fd, TCSANOW,&options))!=0) //设置串口属性
    {
        printf("tcsetattr failed:%s\n", strerror(errno));
        return -4;
    }

    serial->used = 0x01;

    return serial->fd;

}

void comport_close(serial_t *serial)
{
    if(serial != 0)
    {
        printf("Close device \"%s\"\n", serial->dev_name);
        close(serial->fd);
    }

    serial->used = 0x00;
    serial->fd = -1;
}

void set_baudrate(struct termios *opt, int baudrate)
{
    switch(baudrate)
    {
        case 2400:
            _set_baudrate(opt, B2400);
            break;

        case 4800:
            _set_baudrate(opt, B4800);
            break;

        case 9600:
            _set_baudrate(opt, B9600);
            break;

        case 115200:
            _set_baudrate(opt, B115200);
            break;

        default:
            _set_baudrate(opt, B115200);
            break;
    }
}

int _set_baudrate(struct termios *opt,speed_t baudrate)
{
    if(cfsetispeed(opt,baudrate) < 0)
    {
        printf("cfsetispeed failure:%s\n",strerror(errno));
        return -1;
    }

    if(cfsetospeed(opt,baudrate) < 0)
    {
        printf("cfsetospeed failure:%s\n",strerror(errno));
        return -2;
    }

    return 0;
}

void set_stopbit (struct termios *opt, int stopbit)
{
     switch(stopbit)
     {
         case 2:
             opt->c_cflag |= CSTOPB;
             break;

         default:
             opt->c_cflag &= ~CSTOPB;
             break;
     }
}

void set_data_bit (struct termios *opt, int databit)
{
    opt->c_cflag|=(CLOCAL|CREAD ); // CREAD 开启串行数据接收，CLOCAL并打开本地连接模式
    opt->c_cflag &= ~CSIZE;

    switch(databit)
    {
        case 8:
            opt->c_cflag |= CS8;
            break;

        case 7:
            opt->c_cflag |= CS7;
            break;

        case 6:
            opt->c_cflag |= CS6;
            break;

        default:
            opt->c_cflag |= CS8;
            break;
    }
}

void set_parity (struct termios *opt,const char parity)
{
    switch(parity)
    {
        case 'N':
        case 'n':
             opt->c_cflag &= ~PARENB;
             break;

        case 'E':
        case 'e':
              opt->c_cflag |= PARENB;
              opt->c_cflag &= ~PARODD;
              opt->c_cflag |= (INPCK | ISTRIP);
              break;

        case 'O':
        case 'o':
              opt->c_cflag |= (PARENB | PARODD);
              opt->c_cflag |= (INPCK | ISTRIP);
              break;

        default:
              opt->c_cflag &= ~PARENB;
              break;
    }
}

int write_comport(serial_t *serial, char *buf, int buf_size) 
{
    char            *ptr;
    char            *end;
    int             rv = 0;
    int             send = 0;

    if((!buf) || (buf_size<=0))
    {
        printf("%s Invailed parameter!\n", __FUNCTION__) ;
        rv = -1;
        goto cleanup;
    }

    if (0x01 != serial->used)    // Comport not opened ?
    {
        printf("Serail not connected.\n");
        rv = -2;
        goto cleanup;
    }

    if (serial->frag_size < buf_size)
    {
        ptr = buf;
        end = buf+buf_size;

        do
        {
            if (serial->frag_size < (end - ptr))
            {
                send = write(serial->fd, ptr, serial->frag_size);
                if (0 >= send || serial->frag_size != send)
                {
                    rv = -3;
                    goto cleanup;
                }
                ptr += serial->frag_size;
            }
            else
            {
                send = write(serial->fd, ptr, (end - ptr));
                if (0 >= send || (end - ptr) != send)
                {
                    rv = -4;
                    goto cleanup;
                }
                ptr += (end - ptr);
            }
        }while (ptr < end);
    }
    else
    {
        send = write(serial->fd, buf, buf_size);
        if (0 >= send || buf_size != send)
        {
            rv = -5;
            goto cleanup;
        }
    }
cleanup:
    return rv;
}

int read_comport(serial_t *serial, char *buf, int buf_size, int timeout)
{
    fd_set                  rset ;
    struct timeval          tim_out;
    int                     rv;

    if((!buf)||(buf_size<= 0))
    {
        printf("Invaild input\n") ;
        return -1;
    }

    if (0x01 != serial->used)    // Comport not opened ?
    {
        printf("Serail not connected.\n");
        return -2;
    }

    if(timeout) //如果传的参数timeout不为0,用多路复用定时超时时间
    {
        tim_out.tv_sec = (time_t) (timeout / 1000);
        tim_out.tv_usec = (long)(1000 * (timeout % 1000));
        FD_ZERO(&rset) ;
        FD_SET(serial->fd, &rset) ;
        rv = select(serial->fd+1, &rset, NULL, NULL, &tim_out) ;
        if(rv < 0) //select error.
        {
            printf("select() failed: %s\n", strerror(errno)) ;
            return -3;
        }
        if(rv== 0) //select time out.
        {
            return TIMEOUT ;
        }
    }
    usleep(10000); /*   sleep for 10ms for data incoming  */
    rv = read(serial->fd, buf, buf_size) ;
    if(rv <= 0)//read from client failed 
    {
        printf("Read from buf error or disconnect:%s\n", strerror(errno)) ;
        return -5;
    }
    return rv;
}
